Results for SCPCL000296.

Set Up

suppressPackageStartupMessages({
  library(SingleCellExperiment)
  library(ggplot2)
})
Warning: package 'SingleCellExperiment' was built under R version 4.2.2
Warning: package 'GenomicRanges' was built under R version 4.2.2
Warning: package 'S4Vectors' was built under R version 4.2.2
Warning: package 'GenomeInfoDb' was built under R version 4.2.2
theme_set(theme_bw())
# make local data directory if it doesn't exist
if(!dir.exists(params$local_data_dir)){
  dir.create(params$local_data_dir, recursive = TRUE)
}

# build path to annotated sce file 
annotated_sce_file <- glue::glue("{params$library_id}_annotated.rds")
local_annotated_sce_path <- file.path(params$local_data_dir, 
                                      params$sample_id, 
                                      annotated_sce_file)

# if missing any of the annotated sce files, grab them from S3
if(!(file.exists(local_annotated_sce_path))){
  # sync annotated SCE files 
  aws_includes <- glue::glue('--include "*/{annotated_sce_file}"')
  
  sync_call <- glue::glue('aws s3 sync {params$s3_data_dir} {params$local_data_dir} --exclude "*" {aws_includes}')
  system(sync_call, ignore.stdout = TRUE) 
}

# read in all annotated sce 
annotated_sce <- readr::read_rds(local_annotated_sce_path)

Overall celltype assignments

The first thing we will look at is what cell types were assigned for each reference for a given library. We will focus on the top 10 most represented cell types so we don’t over crowd the plot. This can show us if there are any common assignments across references.

Here we are showing the pruned labels. Any cells that do not have labels or have been labeled with NA have been removed from the plot.

plot_annotations <- function(label_type,
                             annotated_sce = annotated_sce){
  label_counts_df <- colData(annotated_sce) |>
    as.data.frame() |>
    tidyr::pivot_longer(cols = starts_with(label_type),
                        names_to = "reference",
                        values_to = "celltype") |>
    # do some string replacements to deal with a few similar assignments across references
    dplyr::mutate(reference = stringr::str_replace(reference, paste0(label_type, "_"), ""),
                  celltype = stringr::str_replace(celltype, "Monocytes", "Monocyte"),
                  celltype = stringr::str_replace(celltype, "Endothelial_cells", "Endothelial cells"),
                  celltype_top = forcats::fct_lump_n(celltype, 10)) |>
    dplyr::count(reference, celltype_top) |>
    tidyr::drop_na()

ggplot(label_counts_df, aes(x = reference, fill = celltype_top, y = n, label = n)) + 
  geom_bar(position = "stack", stat = "identity") +
  labs(
    x = "Reference dataset",
    y = "Number of cells", 
    fill = "Cell type",
    title = label_type
  ) +
  geom_text(size = 3, position = position_stack(vjust = 0.5)) +
   guides(x = guide_axis(angle = 90))
}
labels <- c("label.main", "label.fine", "label.ont")
purrr::map(labels, 
           \(label_type) plot_annotations(label_type,
                                          annotated_sce))
[[1]]


[[2]]


[[3]]

One thing to consider when evaluating the cell type assignments is what options are available for annotations in the reference:

  • The CMP and GMP cells labeled in the HPCA dataset are part of a larger progenitor class
  • The Monaco reference only contains a Progenitor label and does not further classify stem cells or progenitors
  • The Immune Cell Expression reference only contains B cells, Monocytes, NK Cells, and T cell labels

So it’s likely that not all of these references are appropriate for this dataset, which does appear to have mostly HSC and progenitor like cells.

For the rest of this notebook, we will mostly focus on comparing cell type assignments to a complementary reference containing no relevant cells for our sample. We will look at the BlueprintEncodeData and the HumanPrimaryCellAtlasData with all cell types and with the removal of immune cells and compare scores, deltas, and cell type assignments.

Cell type heatmaps

We will make a heatmap to look at all the cells in the dataset and the scores assigned for each cell type label and compare scores for cell types assigned with the full reference vs. the reference without immune cell types.

# function for creating single R heatmaps to look at the labels across references 
compare_heatmaps <- function(all_cells_result,
                             no_immune_result){
  #plot for the all cells reference score
  all_plot <- SingleR::plotScoreHeatmap(all_cells_result,
                                        fontsize = 8,
                                        main = "All cells",
                                        silent = TRUE)
  
  # plot for the no immune cells reference score
  no_immune_plot <- SingleR::plotScoreHeatmap(no_immune_result,
                                              fontsize = 8, 
                                              main = "No immune",
                                              silent = TRUE)
  
  # combine into one plot before returning the object
  combined_plot <- patchwork::wrap_plots(
    list(all_plot$gtable, no_immune_plot$gtable), nrow = 2
  )
  
  return(combined_plot)
  
}
# first just grab the singleR objects so we can directly use those as input to the plotScoreHeatmap function
singler_results <- metadata(annotated_sce)$singler_results

# create heatmaps for the blueprint dataset
blueprint_heatmaps <- compare_heatmaps(
  all_cells_result = singler_results$label.main_BlueprintEncodeData,
  no_immune_result = singler_results$`label.main_no_immune-BlueprintEncodeData`
)

blueprint_heatmaps

# heatmaps for HPCA dataset
hpca_heatmaps <- compare_heatmaps(
  all_cells_result = singler_results$label.main_HumanPrimaryCellAtlasData,
  no_immune_result = singler_results$`label.main_no_immune-HumanPrimaryCellAtlasData`
)

hpca_heatmaps

It looks like regardless of reference, SingleR will always try to assign a cell type and using these references, there appears to be a preference for one cell type over other cell types in the references without immune cells, resulting in what appears like high scores even if that cell type is incorrect.

Delta Score

Next we will look at the delta score which is calculated by determining the difference between the score for the assigned label (prior to fine tuning) and the next highest score. The delta score is less likely to be affected by batch effects, such as library size, and is a more robust measurement to use to compare differences across cells and samples. We would expect that references that perform better to have a higher distribution of delta scores than inappropriate references.

# creates a plot showing the distribution of the desired metric with reference on the x-axis
# color by whether or not labels are pruned or not
# facet by label type (main, fine, or ont)
# we will use this function for looking at delta, median delta, and score distributions
plot_metric_distribution <- function(all_results,
                                    metric_type){
  
  metric_plot <- ggplot(all_results, aes_string(x = "reference", y = metric_type, group = "reference", color = "pruned")) +
    ggforce::geom_sina(size = 0.1, alpha = 0.2) +
    stat_summary(
      aes(group = reference),
      color = "black",
      # median and quartiles for point range
      fun = "median",
      fun.min = function(x) {
        quantile(x, 0.25)
      },
      fun.max = function(x) {
        quantile(x, 0.75)
      },
      geom = "pointrange",
      position = position_dodge(width = 0.9),
      size = 0.1
    ) +
    facet_wrap(vars(label_type))+
    guides(x = guide_axis(angle = 90),
           colour = guide_legend(override.aes = list(size = 2, alpha = 1)))
  
  return(metric_plot)
  
}
# create a large dataframe with all singleR results from all reference annotations
all_results <- singler_results |> 
  purrr::map(as.data.frame) |>
  dplyr::bind_rows(.id = "reference") |>
  tidyr::separate(reference, into = c("label_type", "reference"), sep = "_", extra = "merge") |>
  # classify each label as pruned or not
  dplyr::mutate(pruned = ifelse(is.na(pruned.labels), "Yes", "No"))
# plot the distribution of delta.next for each reference, faceting by label type (main, fine, ont)
plot_metric_distribution(all_results, "delta.next")
Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
ℹ Please use tidy evaluation idioms with `aes()`.
ℹ See also `vignette("ggplot2-in-packages")` for more information.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.

# make the same plot but color by celltypes, looking only at main labels
main_label_results <- all_results |>
  dplyr::filter(label_type == "label.main") |> 
  dplyr::mutate(top_pruned_labels = forcats::fct_lump_n(pruned.labels, 10))

ggplot(main_label_results, aes(x = reference, y = delta.next, group = reference, color = top_pruned_labels)) +
  ggforce::geom_sina(size = 0.1, alpha = 0.5) +
  stat_summary(
    aes(group = reference),
    color = "black",
    # median and quartiles for point range
    fun = "median",
    fun.min = function(x) {
      quantile(x, 0.25)
    },
    fun.max = function(x) {
      quantile(x, 0.75)
    },
    geom = "pointrange",
    position = position_dodge(width = 0.9),
    size = 0.1
  ) +
  guides(x = guide_axis(angle = 90),
         colour = guide_legend(override.aes = list(size = 3, alpha = 1)))

In addition to the delta next, which only looks at the difference between the scores of the top two labels, below we will measure the difference between the top score and the median scores. Then we will plot the distribution of this delta as well.

# function to calculate the median delta for each singleR result
get_median_delta <- function(singler_result){
  
  # grab all the scores
  scores <- singler_result$scores |>
    as.matrix()
  
  # calculate the median delta (max score - median of all scores) 
  median_delta <- rowMaxs(scores) - rowMedians(scores)
  
  # add median into the singler result and return as data frame
  median_delta_result <- singler_result |>
    as.data.frame() |>
    dplyr::select(labels, delta.next, pruned.labels) |>
    dplyr::mutate(median_delta = median_delta)
  
  return(median_delta_result)
}
# get the median delta for each singler object and prep combined data frame for plotting distribution
median_delta_results <- singler_results |> 
  purrr::map(get_median_delta) |>
  dplyr::bind_rows(.id = "reference") |>
  tidyr::separate(reference, into = c("label_type", "reference"), sep = "_", extra = "merge") |>
  # classify each label as pruned or not
  dplyr::mutate(pruned = ifelse(is.na(pruned.labels), "Yes", "No"))
# plot the distribution of median_delta for each reference, faceting by label type (main, fine, ont)
plot_metric_distribution(median_delta_results, "median_delta")

The delta median appears to show more noticeable differences between the references containing cells we would expect in our dataset vs. references that are inappropriate.

SingleR Score

We can also make a plot to show the distribution of scores across all cells. There is some language in the SingleR book that suggests against comparing scores obtained from different references, so if we did want to go this route, we might consider trying to use the method suggested by SingleR to combine scores.

# grab just the scores from the results data frame and rotate for plotting
scores_results <- all_results |>
  tidyr::pivot_longer(cols = starts_with("scores"),
                      names_to = "celltype_score_column",
                      values_to = "score")

plot_metric_distribution(scores_results, "score")
Warning: Removed 21183981 rows containing non-finite values (`stat_sina()`).
Warning: Removed 21183981 rows containing non-finite values (`stat_summary()`).

Clustering comparison

Here we will evaluate the agreement between cluster assignments and cell type assignments. Although we don’t necessarily expect perfect agreement, we do anticipate that any changes in cell type assignment would contribute to the variation present in the dataset. We should see some level of correlation between clustering and cell type assignment if the reference used is an appropriate reference. To measure this, we will calculate the adjusted rand index (ARI) between cluster assignments and predicted labels, where 0 indicates no agreement and 1 indicates perfect agreement.

# pull out PCS
pcs <- reducedDim(annotated_sce, "PCA")

# cluster sce with graph based params and add as column 
annotated_sce$cluster_assignments <- pcs |> bluster::clusterRows(
      bluster::NNGraphParam(cluster.fun = "louvain", type = "jaccard")
    )
# pull out the singleR annotations from the colData
singler_labels_only <- colData(annotated_sce) |>
  as.data.frame() |>
  dplyr::select(starts_with(c("label")))

# for each annotation calculate the ARI between clusters and annotations
cluster_label_ari <- purrr::map_dbl(singler_labels_only, 
                                    \(pruned_labels) 
                                    bluster::pairwiseRand(annotated_sce$cluster_assignments,
                                                          pruned_labels,
                                                          mode = "index")) 

# create a data frame of ari, reference names, and label types for plotting
ari_df <- data.frame(
  ari = cluster_label_ari,
  reference = names(cluster_label_ari)
) |>
  tidyr::separate(reference, into = c("label_type", "reference"), sep = "_", extra = "merge")
# make a simple plot to show the ari for each reference
ggplot(ari_df, aes(x = reference, y = ari, color = label_type)) +
  geom_point() +
   guides(x = guide_axis(angle = 90))

We can also visualize the agreement of cluster assignments to celltype annotation by plotting UMAPs that are colored by cluster assignment and by annotations.

# for simplicity, we will just look at Blueprint, HPCA has a lot of labels so colors get crazy 
references <- c("BlueprintEncodeData",
                "no_immune.BlueprintEncodeData")

# construct a vector of all the label type/ref name combinations
label_types <- unique(all_results$label_type)
full_reference_names <- references |>
  purrr::map(\(ref) glue::glue("{label_types}_{ref}")) |>
  unlist()

# combine with cluster assignments into one vector of names to annotate UMAPs by 
all_plot_labels <- c("cluster_assignments", full_reference_names)

# list of UMAPs colored by clusters and all references 
umaps <- all_plot_labels |> 
  purrr::map(\(label) scater::plotReducedDim(annotated_sce,
                                             dimred = "UMAP",
                                             colour_by = label,
                                             point_size = 0.5,
                                             point_alpha = 0.4))
patchwork::wrap_plots(umaps, ncol = 2)

Comparison across references

Here we will compare annotations across references. We expect that some references will have slightly different annotations, so by looking at all the annotations together we can see how each reference categorizes cells and if they share any similar annotations. First we will look at all the references together and all possible cell types.

# grab all the pruned.labels that are created with a label.main ref
pruned_label_main_df <- all_results |>
  dplyr::filter(label_type == "label.main") |>
  dplyr::select(reference, pruned.labels) |>
  # collapse similar cell types and some sub types 
  dplyr::mutate(collapsed_labels = forcats::fct_collapse(pruned.labels,
                                      "Monocyte" = c("Monocyte",
                                                     "Monocytes"),
                                      "B-cells" = c("B cells", 
                                                    "Pre-B_cell_CD34-",
                                                    "Pro-B_cell_CD34+"),
                                      "T-cells" = c("T_cells",
                                                    "CD8+ T-cells",
                                                    "CD4+ T-cells",
                                                    "CD8+ T cells",
                                                    "CD4+ T cells",
                                                    "T cells",
                                                    "T cells, CD4+",
                                                    "T cells, CD8+"),
                                      "Endothelial cells" = c("Endothelial_cells",
                                                              "Endothelial cells"),
                                      "NK cells" = c("NK_cell",
                                                     "NK cells"),
                                      "HSC" = c("HSC",
                                                "HSC_CD34+"),
                                      "Epithelial cells" = c("Epithelial cells",
                                                             "Epithelial_cells"),
                                      "Myelocyte" = c("Myelocyte",
                                                      "Pro-Myelocyte"),
                                      "Smooth muscle" = c("Smooth muscle",
                                                          "Smooth_muscle_cells")))

# build a matrix with reference as the rows and the celltype as the columns
label_mtx <- table(pruned_label_main_df$reference,
                   pruned_label_main_df$collapsed_labels,
                   useNA = "ifany") |> 
  log1p() # log transform for visual help

pheatmap::pheatmap(label_mtx,
                   cluster_rows = FALSE, 
                   width = 10,
                   fontsize_col = 8)

Now we will explicitly compare our two references that we have been looking at to see how they annotated each of the cells.

# table of blueprint labels vs. hpca labels
blueprint_hpca_comparision <- table(annotated_sce$label.main_BlueprintEncodeData,
                                   annotated_sce$label.main_HumanPrimaryCellAtlasData,
                                   useNA = "ifany") |>
  log1p()

pheatmap::pheatmap(blueprint_hpca_comparision)

# do the same thing but looking at blueprint vs. blueprint without immune cells
no_immune_comparision <- table(annotated_sce$label.main_BlueprintEncodeData,
                               annotated_sce$label.main_no_immune.BlueprintEncodeData,
                               useNA = "ifany") |>
  log1p()

pheatmap::pheatmap(no_immune_comparision)

sessionInfo()
R version 4.2.1 (2022-06-23)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Monterey 12.5.1

Matrix products: default
LAPACK: /Library/Frameworks/R.framework/Versions/4.2/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats4    stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] ggplot2_3.4.2               SingleCellExperiment_1.20.1 SummarizedExperiment_1.28.0
 [4] Biobase_2.58.0              GenomicRanges_1.50.2        GenomeInfoDb_1.34.9        
 [7] IRanges_2.32.0              S4Vectors_0.36.2            BiocGenerics_0.44.0        
[10] MatrixGenerics_1.10.0       matrixStats_0.63.0         

loaded via a namespace (and not attached):
 [1] bitops_1.0-7              RColorBrewer_1.1-3        tools_4.2.1              
 [4] bslib_0.4.2               utf8_1.2.3                R6_2.5.1                 
 [7] irlba_2.3.5.1             vipor_0.4.5               colorspace_2.1-0         
[10] nnet_7.3-17               withr_2.5.0               tidyselect_1.2.0         
[13] gridExtra_2.3             compiler_4.2.1            cli_3.6.1                
[16] BiocNeighbors_1.16.0      DelayedArray_0.24.0       labeling_0.4.2           
[19] sass_0.4.5                scales_1.2.1              readr_2.1.4              
[22] stringr_1.5.0             digest_0.6.31             rmarkdown_2.21           
[25] XVector_0.38.0            scater_1.24.0             pkgconfig_2.0.3          
[28] htmltools_0.5.5           SingleR_1.10.0            sparseMatrixStats_1.10.0 
[31] fastmap_1.1.1             highr_0.10                rlang_1.1.0              
[34] rstudioapi_0.14           DelayedMatrixStats_1.20.0 jquerylib_0.1.4          
[37] farver_2.1.1              generics_0.1.3            jsonlite_1.8.4           
[40] BiocParallel_1.32.6       dplyr_1.1.1               RCurl_1.98-1.12          
[43] magrittr_2.0.3            BiocSingular_1.14.0       scuttle_1.8.4            
[46] modeltools_0.2-23         GenomeInfoDbData_1.2.9    patchwork_1.1.2          
[49] Matrix_1.5-4              ggbeeswarm_0.7.1          Rcpp_1.0.10              
[52] munsell_0.5.0             fansi_1.0.4               viridis_0.6.2            
[55] lifecycle_1.0.3           stringi_1.7.12            yaml_2.3.7               
[58] MASS_7.3-58.3             zlibbioc_1.44.0           flexmix_2.3-19           
[61] grid_4.2.1                ggrepel_0.9.3             parallel_4.2.1           
[64] crayon_1.5.2              forcats_1.0.0             lattice_0.20-45          
[67] cowplot_1.1.1             beachmat_2.14.1           splines_4.2.1            
[70] hms_1.1.3                 knitr_1.42                pillar_1.9.0             
[73] igraph_1.4.2              codetools_0.2-18          ScaledMatrix_1.6.0       
[76] glue_1.6.2                evaluate_0.20             renv_0.15.5              
[79] BiocManager_1.30.20       vctrs_0.6.1               tzdb_0.3.0               
[82] tweenr_2.0.2              gtable_0.3.3              purrr_1.0.1              
[85] polyclip_1.10-4           tidyr_1.3.0               cachem_1.0.7             
[88] xfun_0.38                 ggforce_0.4.1             rsvd_1.0.5               
[91] miQC_1.4.0                viridisLite_0.4.1         tibble_3.2.1             
[94] pheatmap_1.0.12           beeswarm_0.4.0            cluster_2.1.3            
[97] bluster_1.8.0            
LS0tCnRpdGxlOiAiU2luZ2xlUiBjZWxsIHR5cGUgZXhwbG9yYXRpb24gLSBDb21wYXJpbmcgcmVmZXJlbmNlcyB3aXRoIGFuZCB3aXRob3V0IGltbXVuZSBjZWxscyIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKcGFyYW1zOgogIHMzX2RhdGFfZGlyOiAiczM6Ly9uZXh0Zmxvdy1jY2RsLXJlc3VsdHMvc2NwY2EvcHJvY2Vzc2VkL3Jlc3VsdHMvU0NQQ1AwMDAwMDciIAogIGxvY2FsX2RhdGFfZGlyOiAiLi4vZGF0YSIKICBzYW1wbGVfaWQ6ICJTQ1BDUzAwMDIyMiIKICBsaWJyYXJ5X2lkOiAiU0NQQ0wwMDAyOTYiCi0tLQoKUmVzdWx0cyBmb3IgYHIgcGFyYW1zJGxpYnJhcnlfaWRgLgoKIyMgU2V0IFVwCgpgYGB7cn0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsKICBsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KQogIGxpYnJhcnkoZ2dwbG90MikKfSkKdGhlbWVfc2V0KHRoZW1lX2J3KCkpCmBgYAoKYGBge3J9CiMgbWFrZSBsb2NhbCBkYXRhIGRpcmVjdG9yeSBpZiBpdCBkb2Vzbid0IGV4aXN0CmlmKCFkaXIuZXhpc3RzKHBhcmFtcyRsb2NhbF9kYXRhX2RpcikpewogIGRpci5jcmVhdGUocGFyYW1zJGxvY2FsX2RhdGFfZGlyLCByZWN1cnNpdmUgPSBUUlVFKQp9CgojIGJ1aWxkIHBhdGggdG8gYW5ub3RhdGVkIHNjZSBmaWxlIAphbm5vdGF0ZWRfc2NlX2ZpbGUgPC0gZ2x1ZTo6Z2x1ZSgie3BhcmFtcyRsaWJyYXJ5X2lkfV9hbm5vdGF0ZWQucmRzIikKbG9jYWxfYW5ub3RhdGVkX3NjZV9wYXRoIDwtIGZpbGUucGF0aChwYXJhbXMkbG9jYWxfZGF0YV9kaXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhcmFtcyRzYW1wbGVfaWQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFubm90YXRlZF9zY2VfZmlsZSkKCiMgaWYgbWlzc2luZyBhbnkgb2YgdGhlIGFubm90YXRlZCBzY2UgZmlsZXMsIGdyYWIgdGhlbSBmcm9tIFMzCmlmKCEoZmlsZS5leGlzdHMobG9jYWxfYW5ub3RhdGVkX3NjZV9wYXRoKSkpewogICMgc3luYyBhbm5vdGF0ZWQgU0NFIGZpbGVzIAogIGF3c19pbmNsdWRlcyA8LSBnbHVlOjpnbHVlKCctLWluY2x1ZGUgIiove2Fubm90YXRlZF9zY2VfZmlsZX0iJykKICAKICBzeW5jX2NhbGwgPC0gZ2x1ZTo6Z2x1ZSgnYXdzIHMzIHN5bmMge3BhcmFtcyRzM19kYXRhX2Rpcn0ge3BhcmFtcyRsb2NhbF9kYXRhX2Rpcn0gLS1leGNsdWRlICIqIiB7YXdzX2luY2x1ZGVzfScpCiAgc3lzdGVtKHN5bmNfY2FsbCwgaWdub3JlLnN0ZG91dCA9IFRSVUUpIAp9CgojIHJlYWQgaW4gYWxsIGFubm90YXRlZCBzY2UgCmFubm90YXRlZF9zY2UgPC0gcmVhZHI6OnJlYWRfcmRzKGxvY2FsX2Fubm90YXRlZF9zY2VfcGF0aCkKYGBgCgojIyBPdmVyYWxsIGNlbGx0eXBlIGFzc2lnbm1lbnRzIAoKVGhlIGZpcnN0IHRoaW5nIHdlIHdpbGwgbG9vayBhdCBpcyB3aGF0IGNlbGwgdHlwZXMgd2VyZSBhc3NpZ25lZCBmb3IgZWFjaCByZWZlcmVuY2UgZm9yIGEgZ2l2ZW4gbGlicmFyeS4gCldlIHdpbGwgZm9jdXMgb24gdGhlIHRvcCAxMCBtb3N0IHJlcHJlc2VudGVkIGNlbGwgdHlwZXMgc28gd2UgZG9uJ3Qgb3ZlciBjcm93ZCB0aGUgcGxvdC4KVGhpcyBjYW4gc2hvdyB1cyBpZiB0aGVyZSBhcmUgYW55IGNvbW1vbiBhc3NpZ25tZW50cyBhY3Jvc3MgcmVmZXJlbmNlcy4gCgpIZXJlIHdlIGFyZSBzaG93aW5nIHRoZSAqKnBydW5lZCBsYWJlbHMqKi4KQW55IGNlbGxzIHRoYXQgZG8gbm90IGhhdmUgbGFiZWxzIG9yIGhhdmUgYmVlbiBsYWJlbGVkIHdpdGggYE5BYCBoYXZlIGJlZW4gcmVtb3ZlZCBmcm9tIHRoZSBwbG90LgoKYGBge3J9CnBsb3RfYW5ub3RhdGlvbnMgPC0gZnVuY3Rpb24obGFiZWxfdHlwZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbm5vdGF0ZWRfc2NlID0gYW5ub3RhdGVkX3NjZSl7CiAgbGFiZWxfY291bnRzX2RmIDwtIGNvbERhdGEoYW5ub3RhdGVkX3NjZSkgfD4KICAgIGFzLmRhdGEuZnJhbWUoKSB8PgogICAgdGlkeXI6OnBpdm90X2xvbmdlcihjb2xzID0gc3RhcnRzX3dpdGgobGFiZWxfdHlwZSksCiAgICAgICAgICAgICAgICAgICAgICAgIG5hbWVzX3RvID0gInJlZmVyZW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJjZWxsdHlwZSIpIHw+CiAgICAjIGRvIHNvbWUgc3RyaW5nIHJlcGxhY2VtZW50cyB0byBkZWFsIHdpdGggYSBmZXcgc2ltaWxhciBhc3NpZ25tZW50cyBhY3Jvc3MgcmVmZXJlbmNlcwogICAgZHBseXI6Om11dGF0ZShyZWZlcmVuY2UgPSBzdHJpbmdyOjpzdHJfcmVwbGFjZShyZWZlcmVuY2UsIHBhc3RlMChsYWJlbF90eXBlLCAiXyIpLCAiIiksCiAgICAgICAgICAgICAgICAgIGNlbGx0eXBlID0gc3RyaW5ncjo6c3RyX3JlcGxhY2UoY2VsbHR5cGUsICJNb25vY3l0ZXMiLCAiTW9ub2N5dGUiKSwKICAgICAgICAgICAgICAgICAgY2VsbHR5cGUgPSBzdHJpbmdyOjpzdHJfcmVwbGFjZShjZWxsdHlwZSwgIkVuZG90aGVsaWFsX2NlbGxzIiwgIkVuZG90aGVsaWFsIGNlbGxzIiksCiAgICAgICAgICAgICAgICAgIGNlbGx0eXBlX3RvcCA9IGZvcmNhdHM6OmZjdF9sdW1wX24oY2VsbHR5cGUsIDEwKSkgfD4KICAgIGRwbHlyOjpjb3VudChyZWZlcmVuY2UsIGNlbGx0eXBlX3RvcCkgfD4KICAgIHRpZHlyOjpkcm9wX25hKCkKCmdncGxvdChsYWJlbF9jb3VudHNfZGYsIGFlcyh4ID0gcmVmZXJlbmNlLCBmaWxsID0gY2VsbHR5cGVfdG9wLCB5ID0gbiwgbGFiZWwgPSBuKSkgKyAKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJzdGFjayIsIHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgbGFicygKICAgIHggPSAiUmVmZXJlbmNlIGRhdGFzZXQiLAogICAgeSA9ICJOdW1iZXIgb2YgY2VsbHMiLCAKICAgIGZpbGwgPSAiQ2VsbCB0eXBlIiwKICAgIHRpdGxlID0gbGFiZWxfdHlwZQogICkgKwogIGdlb21fdGV4dChzaXplID0gMywgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSkpICsKICAgZ3VpZGVzKHggPSBndWlkZV9heGlzKGFuZ2xlID0gOTApKQp9CmBgYAoKYGBge3IgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OH0KbGFiZWxzIDwtIGMoImxhYmVsLm1haW4iLCAibGFiZWwuZmluZSIsICJsYWJlbC5vbnQiKQpwdXJycjo6bWFwKGxhYmVscywgCiAgICAgICAgICAgXChsYWJlbF90eXBlKSBwbG90X2Fubm90YXRpb25zKGxhYmVsX3R5cGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFubm90YXRlZF9zY2UpKQpgYGAKCgpPbmUgdGhpbmcgdG8gY29uc2lkZXIgd2hlbiBldmFsdWF0aW5nIHRoZSBjZWxsIHR5cGUgYXNzaWdubWVudHMgaXMgd2hhdCBvcHRpb25zIGFyZSBhdmFpbGFibGUgZm9yIGFubm90YXRpb25zIGluIHRoZSByZWZlcmVuY2U6IAoKLSBUaGUgQ01QIGFuZCBHTVAgY2VsbHMgbGFiZWxlZCBpbiB0aGUgSFBDQSBkYXRhc2V0IGFyZSBwYXJ0IG9mIGEgbGFyZ2VyIHByb2dlbml0b3IgY2xhc3MgCi0gVGhlIE1vbmFjbyByZWZlcmVuY2Ugb25seSBjb250YWlucyBhIFByb2dlbml0b3IgbGFiZWwgYW5kIGRvZXMgbm90IGZ1cnRoZXIgY2xhc3NpZnkgc3RlbSBjZWxscyBvciBwcm9nZW5pdG9ycwotIFRoZSBJbW11bmUgQ2VsbCBFeHByZXNzaW9uIHJlZmVyZW5jZSBvbmx5IGNvbnRhaW5zIEIgY2VsbHMsIE1vbm9jeXRlcywgTksgQ2VsbHMsIGFuZCBUIGNlbGwgbGFiZWxzIAoKU28gaXQncyBsaWtlbHkgdGhhdCBub3QgYWxsIG9mIHRoZXNlIHJlZmVyZW5jZXMgYXJlIGFwcHJvcHJpYXRlIGZvciB0aGlzIGRhdGFzZXQsIHdoaWNoIGRvZXMgYXBwZWFyIHRvIGhhdmUgbW9zdGx5IEhTQyBhbmQgcHJvZ2VuaXRvciBsaWtlIGNlbGxzLgoKRm9yIHRoZSByZXN0IG9mIHRoaXMgbm90ZWJvb2ssIHdlIHdpbGwgbW9zdGx5IGZvY3VzIG9uIGNvbXBhcmluZyBjZWxsIHR5cGUgYXNzaWdubWVudHMgdG8gYSBjb21wbGVtZW50YXJ5IHJlZmVyZW5jZSBjb250YWluaW5nIG5vIHJlbGV2YW50IGNlbGxzIGZvciBvdXIgc2FtcGxlLgpXZSB3aWxsIGxvb2sgYXQgdGhlIGBCbHVlcHJpbnRFbmNvZGVEYXRhYCBhbmQgdGhlIGBIdW1hblByaW1hcnlDZWxsQXRsYXNEYXRhYCB3aXRoIGFsbCBjZWxsIHR5cGVzIGFuZCB3aXRoIHRoZSByZW1vdmFsIG9mIGltbXVuZSBjZWxscyBhbmQgY29tcGFyZSBzY29yZXMsIGRlbHRhcywgYW5kIGNlbGwgdHlwZSBhc3NpZ25tZW50cy4KCiMjIENlbGwgdHlwZSBoZWF0bWFwcwoKV2Ugd2lsbCBtYWtlIGEgaGVhdG1hcCB0byBsb29rIGF0IGFsbCB0aGUgY2VsbHMgaW4gdGhlIGRhdGFzZXQgYW5kIHRoZSBzY29yZXMgYXNzaWduZWQgZm9yIGVhY2ggY2VsbCB0eXBlIGxhYmVsIGFuZCBjb21wYXJlIHNjb3JlcyBmb3IgY2VsbCB0eXBlcyBhc3NpZ25lZCB3aXRoIHRoZSBmdWxsIHJlZmVyZW5jZSB2cy4gdGhlIHJlZmVyZW5jZSB3aXRob3V0IGltbXVuZSBjZWxsIHR5cGVzLiAKCmBgYHtyfQojIGZ1bmN0aW9uIGZvciBjcmVhdGluZyBzaW5nbGUgUiBoZWF0bWFwcyB0byBsb29rIGF0IHRoZSBsYWJlbHMgYWNyb3NzIHJlZmVyZW5jZXMgCmNvbXBhcmVfaGVhdG1hcHMgPC0gZnVuY3Rpb24oYWxsX2NlbGxzX3Jlc3VsdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBub19pbW11bmVfcmVzdWx0KXsKICAjcGxvdCBmb3IgdGhlIGFsbCBjZWxscyByZWZlcmVuY2Ugc2NvcmUKICBhbGxfcGxvdCA8LSBTaW5nbGVSOjpwbG90U2NvcmVIZWF0bWFwKGFsbF9jZWxsc19yZXN1bHQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb250c2l6ZSA9IDgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWluID0gIkFsbCBjZWxscyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaWxlbnQgPSBUUlVFKQogIAogICMgcGxvdCBmb3IgdGhlIG5vIGltbXVuZSBjZWxscyByZWZlcmVuY2Ugc2NvcmUKICBub19pbW11bmVfcGxvdCA8LSBTaW5nbGVSOjpwbG90U2NvcmVIZWF0bWFwKG5vX2ltbXVuZV9yZXN1bHQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb250c2l6ZSA9IDgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFpbiA9ICJObyBpbW11bmUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2lsZW50ID0gVFJVRSkKICAKICAjIGNvbWJpbmUgaW50byBvbmUgcGxvdCBiZWZvcmUgcmV0dXJuaW5nIHRoZSBvYmplY3QKICBjb21iaW5lZF9wbG90IDwtIHBhdGNod29yazo6d3JhcF9wbG90cygKICAgIGxpc3QoYWxsX3Bsb3QkZ3RhYmxlLCBub19pbW11bmVfcGxvdCRndGFibGUpLCBucm93ID0gMgogICkKICAKICByZXR1cm4oY29tYmluZWRfcGxvdCkKICAKfQpgYGAKCgpgYGB7ciwgZmlnLmhlaWdodD04fQojIGZpcnN0IGp1c3QgZ3JhYiB0aGUgc2luZ2xlUiBvYmplY3RzIHNvIHdlIGNhbiBkaXJlY3RseSB1c2UgdGhvc2UgYXMgaW5wdXQgdG8gdGhlIHBsb3RTY29yZUhlYXRtYXAgZnVuY3Rpb24Kc2luZ2xlcl9yZXN1bHRzIDwtIG1ldGFkYXRhKGFubm90YXRlZF9zY2UpJHNpbmdsZXJfcmVzdWx0cwoKIyBjcmVhdGUgaGVhdG1hcHMgZm9yIHRoZSBibHVlcHJpbnQgZGF0YXNldApibHVlcHJpbnRfaGVhdG1hcHMgPC0gY29tcGFyZV9oZWF0bWFwcygKICBhbGxfY2VsbHNfcmVzdWx0ID0gc2luZ2xlcl9yZXN1bHRzJGxhYmVsLm1haW5fQmx1ZXByaW50RW5jb2RlRGF0YSwKICBub19pbW11bmVfcmVzdWx0ID0gc2luZ2xlcl9yZXN1bHRzJGBsYWJlbC5tYWluX25vX2ltbXVuZS1CbHVlcHJpbnRFbmNvZGVEYXRhYAopCgpibHVlcHJpbnRfaGVhdG1hcHMKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04fQojIGhlYXRtYXBzIGZvciBIUENBIGRhdGFzZXQKaHBjYV9oZWF0bWFwcyA8LSBjb21wYXJlX2hlYXRtYXBzKAogIGFsbF9jZWxsc19yZXN1bHQgPSBzaW5nbGVyX3Jlc3VsdHMkbGFiZWwubWFpbl9IdW1hblByaW1hcnlDZWxsQXRsYXNEYXRhLAogIG5vX2ltbXVuZV9yZXN1bHQgPSBzaW5nbGVyX3Jlc3VsdHMkYGxhYmVsLm1haW5fbm9faW1tdW5lLUh1bWFuUHJpbWFyeUNlbGxBdGxhc0RhdGFgCikKCmhwY2FfaGVhdG1hcHMKYGBgCgpJdCBsb29rcyBsaWtlIHJlZ2FyZGxlc3Mgb2YgcmVmZXJlbmNlLCBTaW5nbGVSIHdpbGwgYWx3YXlzIHRyeSB0byBhc3NpZ24gYSBjZWxsIHR5cGUgYW5kIHVzaW5nIHRoZXNlIHJlZmVyZW5jZXMsIHRoZXJlIGFwcGVhcnMgdG8gYmUgYSBwcmVmZXJlbmNlIGZvciBvbmUgY2VsbCB0eXBlIG92ZXIgb3RoZXIgY2VsbCB0eXBlcyBpbiB0aGUgcmVmZXJlbmNlcyB3aXRob3V0IGltbXVuZSBjZWxscywgcmVzdWx0aW5nIGluIHdoYXQgYXBwZWFycyBsaWtlIGhpZ2ggc2NvcmVzIGV2ZW4gaWYgdGhhdCBjZWxsIHR5cGUgaXMgaW5jb3JyZWN0LiAKCiMjIERlbHRhIFNjb3JlCgpOZXh0IHdlIHdpbGwgbG9vayBhdCB0aGUgZGVsdGEgc2NvcmUgd2hpY2ggaXMgY2FsY3VsYXRlZCBieSBkZXRlcm1pbmluZyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBzY29yZSBmb3IgdGhlIGFzc2lnbmVkIGxhYmVsIChwcmlvciB0byBmaW5lIHR1bmluZykgYW5kIHRoZSBuZXh0IGhpZ2hlc3Qgc2NvcmUuIApUaGUgZGVsdGEgc2NvcmUgaXMgbGVzcyBsaWtlbHkgdG8gYmUgYWZmZWN0ZWQgYnkgYmF0Y2ggZWZmZWN0cywgc3VjaCBhcyBsaWJyYXJ5IHNpemUsIGFuZCBpcyBhIG1vcmUgcm9idXN0IG1lYXN1cmVtZW50IHRvIHVzZSB0byBjb21wYXJlIGRpZmZlcmVuY2VzIGFjcm9zcyBjZWxscyBhbmQgc2FtcGxlcy4gCldlIHdvdWxkIGV4cGVjdCB0aGF0IHJlZmVyZW5jZXMgdGhhdCBwZXJmb3JtIGJldHRlciB0byBoYXZlIGEgaGlnaGVyIGRpc3RyaWJ1dGlvbiBvZiBkZWx0YSBzY29yZXMgdGhhbiBpbmFwcHJvcHJpYXRlIHJlZmVyZW5jZXMuCgpgYGB7cn0KIyBjcmVhdGVzIGEgcGxvdCBzaG93aW5nIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGRlc2lyZWQgbWV0cmljIHdpdGggcmVmZXJlbmNlIG9uIHRoZSB4LWF4aXMKIyBjb2xvciBieSB3aGV0aGVyIG9yIG5vdCBsYWJlbHMgYXJlIHBydW5lZCBvciBub3QKIyBmYWNldCBieSBsYWJlbCB0eXBlIChtYWluLCBmaW5lLCBvciBvbnQpCiMgd2Ugd2lsbCB1c2UgdGhpcyBmdW5jdGlvbiBmb3IgbG9va2luZyBhdCBkZWx0YSwgbWVkaWFuIGRlbHRhLCBhbmQgc2NvcmUgZGlzdHJpYnV0aW9ucwpwbG90X21ldHJpY19kaXN0cmlidXRpb24gPC0gZnVuY3Rpb24oYWxsX3Jlc3VsdHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldHJpY190eXBlKXsKICAKICBtZXRyaWNfcGxvdCA8LSBnZ3Bsb3QoYWxsX3Jlc3VsdHMsIGFlc19zdHJpbmcoeCA9ICJyZWZlcmVuY2UiLCB5ID0gbWV0cmljX3R5cGUsIGdyb3VwID0gInJlZmVyZW5jZSIsIGNvbG9yID0gInBydW5lZCIpKSArCiAgICBnZ2ZvcmNlOjpnZW9tX3NpbmEoc2l6ZSA9IDAuMSwgYWxwaGEgPSAwLjIpICsKICAgIHN0YXRfc3VtbWFyeSgKICAgICAgYWVzKGdyb3VwID0gcmVmZXJlbmNlKSwKICAgICAgY29sb3IgPSAiYmxhY2siLAogICAgICAjIG1lZGlhbiBhbmQgcXVhcnRpbGVzIGZvciBwb2ludCByYW5nZQogICAgICBmdW4gPSAibWVkaWFuIiwKICAgICAgZnVuLm1pbiA9IGZ1bmN0aW9uKHgpIHsKICAgICAgICBxdWFudGlsZSh4LCAwLjI1KQogICAgICB9LAogICAgICBmdW4ubWF4ID0gZnVuY3Rpb24oeCkgewogICAgICAgIHF1YW50aWxlKHgsIDAuNzUpCiAgICAgIH0sCiAgICAgIGdlb20gPSAicG9pbnRyYW5nZSIsCiAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjkpLAogICAgICBzaXplID0gMC4xCiAgICApICsKICAgIGZhY2V0X3dyYXAodmFycyhsYWJlbF90eXBlKSkrCiAgICBndWlkZXMoeCA9IGd1aWRlX2F4aXMoYW5nbGUgPSA5MCksCiAgICAgICAgICAgY29sb3VyID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDIsIGFscGhhID0gMSkpKQogIAogIHJldHVybihtZXRyaWNfcGxvdCkKICAKfQpgYGAKCgpgYGB7cn0KIyBjcmVhdGUgYSBsYXJnZSBkYXRhZnJhbWUgd2l0aCBhbGwgc2luZ2xlUiByZXN1bHRzIGZyb20gYWxsIHJlZmVyZW5jZSBhbm5vdGF0aW9ucwphbGxfcmVzdWx0cyA8LSBzaW5nbGVyX3Jlc3VsdHMgfD4gCiAgcHVycnI6Om1hcChhcy5kYXRhLmZyYW1lKSB8PgogIGRwbHlyOjpiaW5kX3Jvd3MoLmlkID0gInJlZmVyZW5jZSIpIHw+CiAgdGlkeXI6OnNlcGFyYXRlKHJlZmVyZW5jZSwgaW50byA9IGMoImxhYmVsX3R5cGUiLCAicmVmZXJlbmNlIiksIHNlcCA9ICJfIiwgZXh0cmEgPSAibWVyZ2UiKSB8PgogICMgY2xhc3NpZnkgZWFjaCBsYWJlbCBhcyBwcnVuZWQgb3Igbm90CiAgZHBseXI6Om11dGF0ZShwcnVuZWQgPSBpZmVsc2UoaXMubmEocHJ1bmVkLmxhYmVscyksICJZZXMiLCAiTm8iKSkKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTV9CiMgcGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIGRlbHRhLm5leHQgZm9yIGVhY2ggcmVmZXJlbmNlLCBmYWNldGluZyBieSBsYWJlbCB0eXBlIChtYWluLCBmaW5lLCBvbnQpCnBsb3RfbWV0cmljX2Rpc3RyaWJ1dGlvbihhbGxfcmVzdWx0cywgImRlbHRhLm5leHQiKQpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9NX0KIyBtYWtlIHRoZSBzYW1lIHBsb3QgYnV0IGNvbG9yIGJ5IGNlbGx0eXBlcywgbG9va2luZyBvbmx5IGF0IG1haW4gbGFiZWxzCm1haW5fbGFiZWxfcmVzdWx0cyA8LSBhbGxfcmVzdWx0cyB8PgogIGRwbHlyOjpmaWx0ZXIobGFiZWxfdHlwZSA9PSAibGFiZWwubWFpbiIpIHw+IAogIGRwbHlyOjptdXRhdGUodG9wX3BydW5lZF9sYWJlbHMgPSBmb3JjYXRzOjpmY3RfbHVtcF9uKHBydW5lZC5sYWJlbHMsIDEwKSkKCmdncGxvdChtYWluX2xhYmVsX3Jlc3VsdHMsIGFlcyh4ID0gcmVmZXJlbmNlLCB5ID0gZGVsdGEubmV4dCwgZ3JvdXAgPSByZWZlcmVuY2UsIGNvbG9yID0gdG9wX3BydW5lZF9sYWJlbHMpKSArCiAgZ2dmb3JjZTo6Z2VvbV9zaW5hKHNpemUgPSAwLjEsIGFscGhhID0gMC41KSArCiAgc3RhdF9zdW1tYXJ5KAogICAgYWVzKGdyb3VwID0gcmVmZXJlbmNlKSwKICAgIGNvbG9yID0gImJsYWNrIiwKICAgICMgbWVkaWFuIGFuZCBxdWFydGlsZXMgZm9yIHBvaW50IHJhbmdlCiAgICBmdW4gPSAibWVkaWFuIiwKICAgIGZ1bi5taW4gPSBmdW5jdGlvbih4KSB7CiAgICAgIHF1YW50aWxlKHgsIDAuMjUpCiAgICB9LAogICAgZnVuLm1heCA9IGZ1bmN0aW9uKHgpIHsKICAgICAgcXVhbnRpbGUoeCwgMC43NSkKICAgIH0sCiAgICBnZW9tID0gInBvaW50cmFuZ2UiLAogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuOSksCiAgICBzaXplID0gMC4xCiAgKSArCiAgZ3VpZGVzKHggPSBndWlkZV9heGlzKGFuZ2xlID0gOTApLAogICAgICAgICBjb2xvdXIgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMywgYWxwaGEgPSAxKSkpCmBgYApJbiBhZGRpdGlvbiB0byB0aGUgZGVsdGEgbmV4dCwgd2hpY2ggb25seSBsb29rcyBhdCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBzY29yZXMgb2YgdGhlIHRvcCB0d28gbGFiZWxzLCBiZWxvdyB3ZSB3aWxsIG1lYXN1cmUgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgdG9wIHNjb3JlIGFuZCB0aGUgbWVkaWFuIHNjb3Jlcy4gClRoZW4gd2Ugd2lsbCBwbG90IHRoZSBkaXN0cmlidXRpb24gb2YgdGhpcyBkZWx0YSBhcyB3ZWxsLiAKCmBgYHtyfQojIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSB0aGUgbWVkaWFuIGRlbHRhIGZvciBlYWNoIHNpbmdsZVIgcmVzdWx0CmdldF9tZWRpYW5fZGVsdGEgPC0gZnVuY3Rpb24oc2luZ2xlcl9yZXN1bHQpewogIAogICMgZ3JhYiBhbGwgdGhlIHNjb3JlcwogIHNjb3JlcyA8LSBzaW5nbGVyX3Jlc3VsdCRzY29yZXMgfD4KICAgIGFzLm1hdHJpeCgpCiAgCiAgIyBjYWxjdWxhdGUgdGhlIG1lZGlhbiBkZWx0YSAobWF4IHNjb3JlIC0gbWVkaWFuIG9mIGFsbCBzY29yZXMpIAogIG1lZGlhbl9kZWx0YSA8LSByb3dNYXhzKHNjb3JlcykgLSByb3dNZWRpYW5zKHNjb3JlcykKICAKICAjIGFkZCBtZWRpYW4gaW50byB0aGUgc2luZ2xlciByZXN1bHQgYW5kIHJldHVybiBhcyBkYXRhIGZyYW1lCiAgbWVkaWFuX2RlbHRhX3Jlc3VsdCA8LSBzaW5nbGVyX3Jlc3VsdCB8PgogICAgYXMuZGF0YS5mcmFtZSgpIHw+CiAgICBkcGx5cjo6c2VsZWN0KGxhYmVscywgZGVsdGEubmV4dCwgcHJ1bmVkLmxhYmVscykgfD4KICAgIGRwbHlyOjptdXRhdGUobWVkaWFuX2RlbHRhID0gbWVkaWFuX2RlbHRhKQogIAogIHJldHVybihtZWRpYW5fZGVsdGFfcmVzdWx0KQp9CmBgYAoKYGBge3J9CiMgZ2V0IHRoZSBtZWRpYW4gZGVsdGEgZm9yIGVhY2ggc2luZ2xlciBvYmplY3QgYW5kIHByZXAgY29tYmluZWQgZGF0YSBmcmFtZSBmb3IgcGxvdHRpbmcgZGlzdHJpYnV0aW9uCm1lZGlhbl9kZWx0YV9yZXN1bHRzIDwtIHNpbmdsZXJfcmVzdWx0cyB8PiAKICBwdXJycjo6bWFwKGdldF9tZWRpYW5fZGVsdGEpIHw+CiAgZHBseXI6OmJpbmRfcm93cyguaWQgPSAicmVmZXJlbmNlIikgfD4KICB0aWR5cjo6c2VwYXJhdGUocmVmZXJlbmNlLCBpbnRvID0gYygibGFiZWxfdHlwZSIsICJyZWZlcmVuY2UiKSwgc2VwID0gIl8iLCBleHRyYSA9ICJtZXJnZSIpIHw+CiAgIyBjbGFzc2lmeSBlYWNoIGxhYmVsIGFzIHBydW5lZCBvciBub3QKICBkcGx5cjo6bXV0YXRlKHBydW5lZCA9IGlmZWxzZShpcy5uYShwcnVuZWQubGFiZWxzKSwgIlllcyIsICJObyIpKQpgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0PTV9CiMgcGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIG1lZGlhbl9kZWx0YSBmb3IgZWFjaCByZWZlcmVuY2UsIGZhY2V0aW5nIGJ5IGxhYmVsIHR5cGUgKG1haW4sIGZpbmUsIG9udCkKcGxvdF9tZXRyaWNfZGlzdHJpYnV0aW9uKG1lZGlhbl9kZWx0YV9yZXN1bHRzLCAibWVkaWFuX2RlbHRhIikKYGBgCgpUaGUgZGVsdGEgbWVkaWFuIGFwcGVhcnMgdG8gc2hvdyBtb3JlIG5vdGljZWFibGUgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgcmVmZXJlbmNlcyBjb250YWluaW5nIGNlbGxzIHdlIHdvdWxkIGV4cGVjdCBpbiBvdXIgZGF0YXNldCB2cy4gcmVmZXJlbmNlcyB0aGF0IGFyZSBpbmFwcHJvcHJpYXRlLiAKCiMjIFNpbmdsZVIgU2NvcmUKCldlIGNhbiBhbHNvIG1ha2UgYSBwbG90IHRvIHNob3cgdGhlIGRpc3RyaWJ1dGlvbiBvZiBzY29yZXMgYWNyb3NzIGFsbCBjZWxscy4KVGhlcmUgaXMgc29tZSBsYW5ndWFnZSBpbiB0aGUgU2luZ2xlUiBib29rIHRoYXQgc3VnZ2VzdHMgYWdhaW5zdCBjb21wYXJpbmcgc2NvcmVzIG9idGFpbmVkIGZyb20gZGlmZmVyZW50IHJlZmVyZW5jZXMsIHNvIGlmIHdlIGRpZCB3YW50IHRvIGdvIHRoaXMgcm91dGUsIHdlIG1pZ2h0IGNvbnNpZGVyIHRyeWluZyB0byB1c2UgdGhlIG1ldGhvZCBzdWdnZXN0ZWQgYnkgW1NpbmdsZVIgdG8gY29tYmluZSBzY29yZXNdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzL3JlbGVhc2UvU2luZ2xlUkJvb2svdXNpbmctbXVsdGlwbGUtcmVmZXJlbmNlcy5odG1sI2NvbWJpbmluZy1pbmZlcmVuY2VzLWZyb20taW5kaXZpZHVhbC1yZWZlcmVuY2VzKS4KCmBgYHtyLCBmaWcuaGVpZ2h0PTV9CiMgZ3JhYiBqdXN0IHRoZSBzY29yZXMgZnJvbSB0aGUgcmVzdWx0cyBkYXRhIGZyYW1lIGFuZCByb3RhdGUgZm9yIHBsb3R0aW5nCnNjb3Jlc19yZXN1bHRzIDwtIGFsbF9yZXN1bHRzIHw+CiAgdGlkeXI6OnBpdm90X2xvbmdlcihjb2xzID0gc3RhcnRzX3dpdGgoInNjb3JlcyIpLAogICAgICAgICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiY2VsbHR5cGVfc2NvcmVfY29sdW1uIiwKICAgICAgICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJzY29yZSIpCgpwbG90X21ldHJpY19kaXN0cmlidXRpb24oc2NvcmVzX3Jlc3VsdHMsICJzY29yZSIpCmBgYAoKIyMgQ2x1c3RlcmluZyBjb21wYXJpc29uIAoKSGVyZSB3ZSB3aWxsIGV2YWx1YXRlIHRoZSBhZ3JlZW1lbnQgYmV0d2VlbiBjbHVzdGVyIGFzc2lnbm1lbnRzIGFuZCBjZWxsIHR5cGUgYXNzaWdubWVudHMuCkFsdGhvdWdoIHdlIGRvbid0IG5lY2Vzc2FyaWx5IGV4cGVjdCBwZXJmZWN0IGFncmVlbWVudCwgd2UgZG8gYW50aWNpcGF0ZSB0aGF0IGFueSBjaGFuZ2VzIGluIGNlbGwgdHlwZSBhc3NpZ25tZW50IHdvdWxkIGNvbnRyaWJ1dGUgdG8gdGhlIHZhcmlhdGlvbiBwcmVzZW50IGluIHRoZSBkYXRhc2V0LgpXZSBzaG91bGQgc2VlIHNvbWUgbGV2ZWwgb2YgY29ycmVsYXRpb24gYmV0d2VlbiBjbHVzdGVyaW5nIGFuZCBjZWxsIHR5cGUgYXNzaWdubWVudCBpZiB0aGUgcmVmZXJlbmNlIHVzZWQgaXMgYW4gYXBwcm9wcmlhdGUgcmVmZXJlbmNlLiAKVG8gbWVhc3VyZSB0aGlzLCB3ZSB3aWxsIGNhbGN1bGF0ZSB0aGUgYWRqdXN0ZWQgcmFuZCBpbmRleCAoQVJJKSBiZXR3ZWVuIGNsdXN0ZXIgYXNzaWdubWVudHMgYW5kIHByZWRpY3RlZCBsYWJlbHMsIHdoZXJlIDAgaW5kaWNhdGVzIG5vIGFncmVlbWVudCBhbmQgMSBpbmRpY2F0ZXMgcGVyZmVjdCBhZ3JlZW1lbnQuCgpgYGB7cn0KIyBwdWxsIG91dCBQQ1MKcGNzIDwtIHJlZHVjZWREaW0oYW5ub3RhdGVkX3NjZSwgIlBDQSIpCgojIGNsdXN0ZXIgc2NlIHdpdGggZ3JhcGggYmFzZWQgcGFyYW1zIGFuZCBhZGQgYXMgY29sdW1uIAphbm5vdGF0ZWRfc2NlJGNsdXN0ZXJfYXNzaWdubWVudHMgPC0gcGNzIHw+IGJsdXN0ZXI6OmNsdXN0ZXJSb3dzKAogICAgICBibHVzdGVyOjpOTkdyYXBoUGFyYW0oY2x1c3Rlci5mdW4gPSAibG91dmFpbiIsIHR5cGUgPSAiamFjY2FyZCIpCiAgICApCmBgYAoKYGBge3J9CiMgcHVsbCBvdXQgdGhlIHNpbmdsZVIgYW5ub3RhdGlvbnMgZnJvbSB0aGUgY29sRGF0YQpzaW5nbGVyX2xhYmVsc19vbmx5IDwtIGNvbERhdGEoYW5ub3RhdGVkX3NjZSkgfD4KICBhcy5kYXRhLmZyYW1lKCkgfD4KICBkcGx5cjo6c2VsZWN0KHN0YXJ0c193aXRoKGMoImxhYmVsIikpKQoKIyBmb3IgZWFjaCBhbm5vdGF0aW9uIGNhbGN1bGF0ZSB0aGUgQVJJIGJldHdlZW4gY2x1c3RlcnMgYW5kIGFubm90YXRpb25zCmNsdXN0ZXJfbGFiZWxfYXJpIDwtIHB1cnJyOjptYXBfZGJsKHNpbmdsZXJfbGFiZWxzX29ubHksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcKHBydW5lZF9sYWJlbHMpIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBibHVzdGVyOjpwYWlyd2lzZVJhbmQoYW5ub3RhdGVkX3NjZSRjbHVzdGVyX2Fzc2lnbm1lbnRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJ1bmVkX2xhYmVscywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGUgPSAiaW5kZXgiKSkgCgojIGNyZWF0ZSBhIGRhdGEgZnJhbWUgb2YgYXJpLCByZWZlcmVuY2UgbmFtZXMsIGFuZCBsYWJlbCB0eXBlcyBmb3IgcGxvdHRpbmcKYXJpX2RmIDwtIGRhdGEuZnJhbWUoCiAgYXJpID0gY2x1c3Rlcl9sYWJlbF9hcmksCiAgcmVmZXJlbmNlID0gbmFtZXMoY2x1c3Rlcl9sYWJlbF9hcmkpCikgfD4KICB0aWR5cjo6c2VwYXJhdGUocmVmZXJlbmNlLCBpbnRvID0gYygibGFiZWxfdHlwZSIsICJyZWZlcmVuY2UiKSwgc2VwID0gIl8iLCBleHRyYSA9ICJtZXJnZSIpCiAgCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTV9CiMgbWFrZSBhIHNpbXBsZSBwbG90IHRvIHNob3cgdGhlIGFyaSBmb3IgZWFjaCByZWZlcmVuY2UKZ2dwbG90KGFyaV9kZiwgYWVzKHggPSByZWZlcmVuY2UsIHkgPSBhcmksIGNvbG9yID0gbGFiZWxfdHlwZSkpICsKICBnZW9tX3BvaW50KCkgKwogICBndWlkZXMoeCA9IGd1aWRlX2F4aXMoYW5nbGUgPSA5MCkpCmBgYApXZSBjYW4gYWxzbyB2aXN1YWxpemUgdGhlIGFncmVlbWVudCBvZiBjbHVzdGVyIGFzc2lnbm1lbnRzIHRvIGNlbGx0eXBlIGFubm90YXRpb24gYnkgcGxvdHRpbmcgVU1BUHMgdGhhdCBhcmUgY29sb3JlZCBieSBjbHVzdGVyIGFzc2lnbm1lbnQgYW5kIGJ5IGFubm90YXRpb25zLgoKYGBge3IsIGZpZy5oZWlnaHQgPSAxMCwgZmlnLndpZHRoPTEwfQojIGZvciBzaW1wbGljaXR5LCB3ZSB3aWxsIGp1c3QgbG9vayBhdCBCbHVlcHJpbnQsIEhQQ0EgaGFzIGEgbG90IG9mIGxhYmVscyBzbyBjb2xvcnMgZ2V0IGNyYXp5IApyZWZlcmVuY2VzIDwtIGMoIkJsdWVwcmludEVuY29kZURhdGEiLAogICAgICAgICAgICAgICAgIm5vX2ltbXVuZS5CbHVlcHJpbnRFbmNvZGVEYXRhIikKCiMgY29uc3RydWN0IGEgdmVjdG9yIG9mIGFsbCB0aGUgbGFiZWwgdHlwZS9yZWYgbmFtZSBjb21iaW5hdGlvbnMKbGFiZWxfdHlwZXMgPC0gdW5pcXVlKGFsbF9yZXN1bHRzJGxhYmVsX3R5cGUpCmZ1bGxfcmVmZXJlbmNlX25hbWVzIDwtIHJlZmVyZW5jZXMgfD4KICBwdXJycjo6bWFwKFwocmVmKSBnbHVlOjpnbHVlKCJ7bGFiZWxfdHlwZXN9X3tyZWZ9IikpIHw+CiAgdW5saXN0KCkKCiMgY29tYmluZSB3aXRoIGNsdXN0ZXIgYXNzaWdubWVudHMgaW50byBvbmUgdmVjdG9yIG9mIG5hbWVzIHRvIGFubm90YXRlIFVNQVBzIGJ5IAphbGxfcGxvdF9sYWJlbHMgPC0gYygiY2x1c3Rlcl9hc3NpZ25tZW50cyIsIGZ1bGxfcmVmZXJlbmNlX25hbWVzKQoKIyBsaXN0IG9mIFVNQVBzIGNvbG9yZWQgYnkgY2x1c3RlcnMgYW5kIGFsbCByZWZlcmVuY2VzIAp1bWFwcyA8LSBhbGxfcGxvdF9sYWJlbHMgfD4gCiAgcHVycnI6Om1hcChcKGxhYmVsKSBzY2F0ZXI6OnBsb3RSZWR1Y2VkRGltKGFubm90YXRlZF9zY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpbXJlZCA9ICJVTUFQIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3VyX2J5ID0gbGFiZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvaW50X3NpemUgPSAwLjUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvaW50X2FscGhhID0gMC40KSkKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHVtYXBzLCBuY29sID0gMikKCmBgYAoKIyMgQ29tcGFyaXNvbiBhY3Jvc3MgcmVmZXJlbmNlcyAKCkhlcmUgd2Ugd2lsbCBjb21wYXJlIGFubm90YXRpb25zIGFjcm9zcyByZWZlcmVuY2VzLgpXZSBleHBlY3QgdGhhdCBzb21lIHJlZmVyZW5jZXMgd2lsbCBoYXZlIHNsaWdodGx5IGRpZmZlcmVudCBhbm5vdGF0aW9ucywgc28gYnkgbG9va2luZyBhdCBhbGwgdGhlIGFubm90YXRpb25zIHRvZ2V0aGVyIHdlIGNhbiBzZWUgaG93IGVhY2ggcmVmZXJlbmNlIGNhdGVnb3JpemVzIGNlbGxzIGFuZCBpZiB0aGV5IHNoYXJlIGFueSBzaW1pbGFyIGFubm90YXRpb25zLgpGaXJzdCB3ZSB3aWxsIGxvb2sgYXQgYWxsIHRoZSByZWZlcmVuY2VzIHRvZ2V0aGVyIGFuZCBhbGwgcG9zc2libGUgY2VsbCB0eXBlcy4gCgpgYGB7ciwgZmlnLndpZHRoPTh9CiMgZ3JhYiBhbGwgdGhlIHBydW5lZC5sYWJlbHMgdGhhdCBhcmUgY3JlYXRlZCB3aXRoIGEgbGFiZWwubWFpbiByZWYKcHJ1bmVkX2xhYmVsX21haW5fZGYgPC0gYWxsX3Jlc3VsdHMgfD4KICBkcGx5cjo6ZmlsdGVyKGxhYmVsX3R5cGUgPT0gImxhYmVsLm1haW4iKSB8PgogIGRwbHlyOjpzZWxlY3QocmVmZXJlbmNlLCBwcnVuZWQubGFiZWxzKSB8PgogICMgY29sbGFwc2Ugc2ltaWxhciBjZWxsIHR5cGVzIGFuZCBzb21lIHN1YiB0eXBlcyAKICBkcGx5cjo6bXV0YXRlKGNvbGxhcHNlZF9sYWJlbHMgPSBmb3JjYXRzOjpmY3RfY29sbGFwc2UocHJ1bmVkLmxhYmVscywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTW9ub2N5dGUiID0gYygiTW9ub2N5dGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJNb25vY3l0ZXMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQi1jZWxscyIgPSBjKCJCIGNlbGxzIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUHJlLUJfY2VsbF9DRDM0LSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUHJvLUJfY2VsbF9DRDM0KyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJULWNlbGxzIiA9IGMoIlRfY2VsbHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNEOCsgVC1jZWxscyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ0Q0KyBULWNlbGxzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDRDgrIFQgY2VsbHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNENCsgVCBjZWxscyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVCBjZWxscyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVCBjZWxscywgQ0Q0KyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVCBjZWxscywgQ0Q4KyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJFbmRvdGhlbGlhbCBjZWxscyIgPSBjKCJFbmRvdGhlbGlhbF9jZWxscyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkVuZG90aGVsaWFsIGNlbGxzIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5LIGNlbGxzIiA9IGMoIk5LX2NlbGwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJOSyBjZWxscyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJIU0MiID0gYygiSFNDIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkhTQ19DRDM0KyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJFcGl0aGVsaWFsIGNlbGxzIiA9IGMoIkVwaXRoZWxpYWwgY2VsbHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkVwaXRoZWxpYWxfY2VsbHMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTXllbG9jeXRlIiA9IGMoIk15ZWxvY3l0ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJQcm8tTXllbG9jeXRlIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNtb290aCBtdXNjbGUiID0gYygiU21vb3RoIG11c2NsZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiU21vb3RoX211c2NsZV9jZWxscyIpKSkKCiMgYnVpbGQgYSBtYXRyaXggd2l0aCByZWZlcmVuY2UgYXMgdGhlIHJvd3MgYW5kIHRoZSBjZWxsdHlwZSBhcyB0aGUgY29sdW1ucwpsYWJlbF9tdHggPC0gdGFibGUocHJ1bmVkX2xhYmVsX21haW5fZGYkcmVmZXJlbmNlLAogICAgICAgICAgICAgICAgICAgcHJ1bmVkX2xhYmVsX21haW5fZGYkY29sbGFwc2VkX2xhYmVscywKICAgICAgICAgICAgICAgICAgIHVzZU5BID0gImlmYW55IikgfD4gCiAgbG9nMXAoKSAjIGxvZyB0cmFuc2Zvcm0gZm9yIHZpc3VhbCBoZWxwCgpwaGVhdG1hcDo6cGhlYXRtYXAobGFiZWxfbXR4LAogICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgd2lkdGggPSAxMCwKICAgICAgICAgICAgICAgICAgIGZvbnRzaXplX2NvbCA9IDgpCmBgYApOb3cgd2Ugd2lsbCBleHBsaWNpdGx5IGNvbXBhcmUgb3VyIHR3byByZWZlcmVuY2VzIHRoYXQgd2UgaGF2ZSBiZWVuIGxvb2tpbmcgYXQgdG8gc2VlIGhvdyB0aGV5IGFubm90YXRlZCBlYWNoIG9mIHRoZSBjZWxscy4KCmBgYHtyfQojIHRhYmxlIG9mIGJsdWVwcmludCBsYWJlbHMgdnMuIGhwY2EgbGFiZWxzCmJsdWVwcmludF9ocGNhX2NvbXBhcmlzaW9uIDwtIHRhYmxlKGFubm90YXRlZF9zY2UkbGFiZWwubWFpbl9CbHVlcHJpbnRFbmNvZGVEYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFubm90YXRlZF9zY2UkbGFiZWwubWFpbl9IdW1hblByaW1hcnlDZWxsQXRsYXNEYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZU5BID0gImlmYW55IikgfD4KICBsb2cxcCgpCgpwaGVhdG1hcDo6cGhlYXRtYXAoYmx1ZXByaW50X2hwY2FfY29tcGFyaXNpb24pCmBgYAoKYGBge3J9CiMgZG8gdGhlIHNhbWUgdGhpbmcgYnV0IGxvb2tpbmcgYXQgYmx1ZXByaW50IHZzLiBibHVlcHJpbnQgd2l0aG91dCBpbW11bmUgY2VsbHMKbm9faW1tdW5lX2NvbXBhcmlzaW9uIDwtIHRhYmxlKGFubm90YXRlZF9zY2UkbGFiZWwubWFpbl9CbHVlcHJpbnRFbmNvZGVEYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYW5ub3RhdGVkX3NjZSRsYWJlbC5tYWluX25vX2ltbXVuZS5CbHVlcHJpbnRFbmNvZGVEYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNlTkEgPSAiaWZhbnkiKSB8PgogIGxvZzFwKCkKCnBoZWF0bWFwOjpwaGVhdG1hcChub19pbW11bmVfY29tcGFyaXNpb24pCmBgYAoKCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoKCg==